/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


#include "cm_local.h"


#define CLIP_MODELS_HASH_SIZE			MAX_CLIP_MODELS / 4

static clipModel_t *	cm_boxModel;
static cmInlineModel_t	cm_boxInlineModel;
static odPlane *		cm_boxSides;
static cmMaterial_t *	cm_boxMaterial;
static odPlane *		cm_boxPlanes;
static cmBrush_t *		cm_boxBrush;

static clipModel_t *	cm_modelInstances[MAX_ENTITIES];
static int				cm_numModelInstances;

static int				cm_deferredFreeInstances[MAX_ENTITIES];

static clipModel_t *	cm_modelsHashTable[CLIP_MODELS_HASH_SIZE];
static clipModel_t *	cm_models[MAX_CLIP_MODELS];
static int				cm_numModels;


/*
 ==================
 CM_MultiplyJoints
 ==================
*/
static void CM_MultiplyJoints (int numJoints, cmJoint_t *invJoints, odJointMat *joints, odJointMat *out){

	int		i;

	if (joints){
		// Multiply the joints by the inverse model joints
		for (i = 0; i < numJoints; i++)
			out[i] = joints[i] * invJoints[i].matrix;
	}
	else {
		// Set the joints to identity for default pose
		for (i = 0; i < numJoints; i++)
			out[i].Identity();
	}
}

/*
 ==================
 CM_ComputeTriangleSurface
 ==================
*/
static void CM_ComputeTriangleSurface (cmTriangleSurface_t *surface, odJointMat *joints){

	int			*index;
	cmVertex_t	*vertex;
	cmWeight_t	*weight;
	odJointMat	weightedJoint;
	odPlane		*facePlane, *edgePlane;
	odVec3		*v0, *v1, *v2;
	int			i, j;

	// Clear the bounds
	surface->bounds.Clear();

	for (i = 0, vertex = surface->vertices; i < surface->numVertices; i++, vertex++){
		// Compute a weighted joint for this vertex
		weight = surface->weights + vertex->firstWeight;

		weightedJoint = joints[weight->joint] * weight->jointWeight;

		if (vertex->numWeights != 1){
			weight++;

			for (j = 1; j < vertex->numWeights; j++, weight++)
				weightedJoint += joints[weight->joint] * weight->jointWeight;
		}

		// Transform position vector
		weightedJoint.TransformVector(vertex->baseXyz, surface->transformedXyz[i]);

		// Also add to bounds
		surface->bounds.AddPoint(surface->transformedXyz[i]);
	}

	// Set up the sides
	for (i = 0; i < 6; i++){
		surface->sides[i].normal.Clear();

		if (i & 1){
			surface->sides[i].normal[i>>1] = -1.0f;
			surface->sides[i].dist = -surface->bounds[0][i>>1];
		}
		else {
			surface->sides[i].normal[i>>1] = 1.0f;
			surface->sides[i].dist = surface->bounds[1][i>>1];
		}

		surface->sides[i].SetType();
		surface->sides[i].SetSignBits();
	}

	// Compute triangle planes
	for (i = 0, index = surface->indices, facePlane = surface->facePlanes, edgePlane = surface->edgePlanes; i < surface->numIndices; i += 3, index += 3, facePlane++, edgePlane += 3){
		v0 = &surface->transformedXyz[index[0]];
		v1 = &surface->transformedXyz[index[1]];
		v2 = &surface->transformedXyz[index[2]];

		// Compute the face plane
		facePlane->normal.Cross(*v2 - *v0, *v1 - *v0);
		facePlane->normal.NormalizeFast();

		facePlane->dist = v0->Dot(facePlane->normal);

		facePlane->SetType();
		facePlane->SetSignBits();

		// Compute the first edge plane
		edgePlane[0].normal.Cross(facePlane->normal, *v1 - *v0);
		edgePlane[0].normal.NormalizeFast();

		edgePlane[0].dist = v0->Dot(edgePlane[0].normal);

		edgePlane[0].SetType();
		edgePlane[0].SetSignBits();

		// Compute the second edge plane
		edgePlane[1].normal.Cross(facePlane->normal, *v2 - *v1);
		edgePlane[1].normal.NormalizeFast();

		edgePlane[1].dist = v1->Dot(edgePlane[1].normal);

		edgePlane[1].SetType();
		edgePlane[1].SetSignBits();

		// Compute the third edge plane
		edgePlane[2].normal.Cross(facePlane->normal, *v0 - *v2);
		edgePlane[2].normal.NormalizeFast();

		edgePlane[2].dist = v2->Dot(edgePlane[2].normal);

		edgePlane[2].SetType();
		edgePlane[2].SetSignBits();
	}
}


/*
 ==============================================================================

 CM LOADING

 ==============================================================================
*/


/*
 ==================
 CM_LoadStaticModel
 ==================
*/
static bool CM_LoadStaticModel (const char *name, cmStaticModel_t **model, int *size){

	byte				*data;
	cmHeader_t			*inHeader;
	cmMesh_t			*inMesh;
	cmPlane_t			*inPlane;
	cmFacet_t			*inFacet;
	cmStaticModel_t		*outModel;
	cmSurface_t			*outSurface;
	cmMaterial_t		*outMaterial;
	odPlane				*outPlane;
	cmSurfaceFacet_t	*outFacet;
	int					ident, version;
	int					numMeshes, numPlanes, numFacets;
	int					i, j, k;

	// Load the file
	FS_ReadFile(name, (void **)&data);
	if (!data)
		return false;

	*model = outModel = (cmStaticModel_t *)Mem_Alloc(sizeof(cmStaticModel_t), TAG_COLLISION);
	*size = sizeof(cmStaticModel_t);

	// Byte swap the header fields and sanity check
	inHeader = (cmHeader_t *)data;

	ident = LittleLong(inHeader->ident);
	if (ident != CM_IDENT)
		Com_Error(false, "CM_LoadStaticModel: wrong file id (%s)", name);

	version = LittleLong(inHeader->version);
	if (version != CM_VERSION)
		Com_Error(false, "CM_LoadStaticModel: wrong version number (%i should be %i) (%s)", version, CM_VERSION, name);

	// Clear the content flags
	outModel->contentFlags = 0;

	// Load the bounds
	for (i = 0; i < 3; i++){
		outModel->bounds[0][i] = LittleFloat(inHeader->bounds[0][i]);
		outModel->bounds[1][i] = LittleFloat(inHeader->bounds[1][i]);
	}

	// Set up the sides
	for (i = 0; i < 6; i++){
		outModel->sides[i].normal.Clear();

		if (i & 1){
			outModel->sides[i].normal[i>>1] = -1.0f;
			outModel->sides[i].dist = -outModel->bounds[0][i>>1];
		}
		else {
			outModel->sides[i].normal[i>>1] = 1.0f;
			outModel->sides[i].dist = outModel->bounds[1][i>>1];
		}

		outModel->sides[i].SetType();
		outModel->sides[i].SetSignBits();
	}

	// Check the number of meshes
	numMeshes = LittleLong(inHeader->numMeshes);
	if (numMeshes < 1)
		Com_Error(false, "CM_LoadStaticModel: no meshes (%s)", name);
	else if (numMeshes > CM_MAX_MESHES)
		Com_Error(false, "CM_LoadStaticModel: too many meshes (%s)", name);

	outModel->numSurfaces = numMeshes;

	// Load the meshes
	inMesh = (cmMesh_t *)(data + LittleLong(inHeader->ofsMeshes));

	outModel->surfaces = outSurface = (cmSurface_t *)Mem_Alloc(outModel->numSurfaces * sizeof(cmSurface_t), TAG_COLLISION);
	*size += outModel->numSurfaces * sizeof(cmSurface_t);

	for (i = 0; i < numMeshes; i++, outSurface++){
		outSurface->type = ST_TRISOUP;

		// Load the material
		outSurface->material = outMaterial = (cmMaterial_t *)Mem_Alloc(sizeof(cmMaterial_t), TAG_COLLISION);
		*size += sizeof(cmMaterial_t);

		Str_Copy(outMaterial->name, inMesh->material, sizeof(outMaterial->name));

		outMaterial->surfaceFlags = LittleLong(inMesh->surfaceFlags);
		outMaterial->contentFlags = LittleLong(inMesh->contentFlags);

		// Load the bounds
		for (j = 0; j < 3; j++){
			outSurface->bounds[0][j] = LittleFloat(inMesh->bounds[0][j]);
			outSurface->bounds[1][j] = LittleFloat(inMesh->bounds[1][j]);
		}

		// Set up the sides
		for (j = 0; j < 6; j++){
			outSurface->sides[j].normal.Clear();

			if (j & 1){
				outSurface->sides[j].normal[j>>1] = -1.0f;
				outSurface->sides[j].dist = -outSurface->bounds[0][j>>1];
			}
			else {
				outSurface->sides[j].normal[j>>1] = 1.0f;
				outSurface->sides[j].dist = outSurface->bounds[1][j>>1];
			}

			outSurface->sides[j].SetType();
			outSurface->sides[j].SetSignBits();
		}

		// Check the number of planes
		numPlanes = LittleLong(inMesh->numPlanes);
		if (numPlanes < 1)
			Com_Error(false, "CM_LoadStaticModel: no planes (%s)", name);
		else if (numPlanes > CM_MAX_PLANES)
			Com_Error(false, "CM_LoadStaticModel: too many planes (%s)", name);

		outSurface->numPlanes = numPlanes;

		// Check the number of facets
		numFacets = LittleLong(inMesh->numFacets);
		if (numFacets < 1)
			Com_Error(false, "CM_LoadStaticModel: no facets (%s)", name);
		else if (numFacets > CM_MAX_FACETS)
			Com_Error(false, "CM_LoadStaticModel: too many facets (%s)", name);

		outSurface->numFacets = numFacets;

		// Load the planes
		inPlane = (cmPlane_t *)((byte *)inMesh + LittleLong(inMesh->ofsPlanes));

		outSurface->planes = outPlane = (odPlane *)Mem_Alloc(outSurface->numPlanes * sizeof(odPlane), TAG_COLLISION);
		*size += outSurface->numPlanes * sizeof(odPlane);

		for (j = 0; j < numPlanes; j++, inPlane++, outPlane++){
			outPlane->normal[0] = LittleFloat(inPlane->normal[0]);
			outPlane->normal[1] = LittleFloat(inPlane->normal[1]);
			outPlane->normal[2] = LittleFloat(inPlane->normal[2]);

			outPlane->dist = LittleFloat(inPlane->dist);

			outPlane->SetType();
			outPlane->SetSignBits();
		}

		// Load the facets
		inFacet = (cmFacet_t *)((byte *)inMesh + LittleLong(inMesh->ofsFacets));

		outSurface->facets = outFacet = (cmSurfaceFacet_t *)Mem_Alloc(outSurface->numFacets * sizeof(cmSurfaceFacet_t), TAG_COLLISION);
		*size += outSurface->numFacets * sizeof(cmSurfaceFacet_t);

		for (j = 0; j < numFacets; j++, inFacet++, outFacet++){
			outFacet->facetPlane = LittleLong(inFacet->facetPlane);

			outFacet->numBorders = LittleLong(inFacet->numBorders);
			outFacet->borderInward = LittleLong(inFacet->borderInward);

			for (k = 0; k < 21; k++)
				outFacet->borderPlanes[k] = LittleShort(inFacet->borderPlanes[k]);
		}

		// Clear counters
		outSurface->contentsCount = 0;
		outSurface->traceCount = 0;

		// OR in the content flags from this surface
		outModel->contentFlags |= outMaterial->contentFlags;

		// Go to the next mesh
		inMesh = (cmMesh_t *)((byte *)inMesh + LittleLong(inMesh->ofsEnd));
	}

	// Free file data
	FS_FreeFile(data);

	return true;
}


/*
 ==============================================================================

 MD5 LOADING

 ==============================================================================
*/


/*
 ==================
 CM_FinishDynamicModel
 ==================
*/
static void CM_FinishDynamicModel (cmDynamicModel_t *model, int *size){

	cmJoint_t			*joint;
	cmTriangleSurface_t	*surface;
	cmVertex_t			*vertex;
	cmWeight_t			*weight;
	odVec3				tmp;
	int					i, j, k;

	// Clear the content flags
	model->contentFlags = 0;

	// Set up the bounds
	model->bounds.Zero();

	// Set up the sides
	for (i = 0; i < 6; i++){
		model->sides[i].normal.Clear();

		if (i & 1)
			model->sides[i].normal[i>>1] = -1.0f;
		else
			model->sides[i].normal[i>>1] = 1.0f;

		model->sides[i].dist = 0.0f;

		model->sides[i].SetType();
		model->sides[i].SetSignBits();
	}

	// Set up the surfaces
	for (i = 0, surface = model->surfaces; i < model->numSurfaces; i++, surface++){
		// Set up the bounds
		surface->bounds.Zero();

		// Set up the sides
		for (j = 0; j < 6; j++){
			surface->sides[j].normal.Clear();

			if (j & 1)
				surface->sides[j].normal[j>>1] = -1.0f;
			else
				surface->sides[j].normal[j>>1] = 1.0f;

			model->sides[j].dist = 0.0f;

			surface->sides[j].SetType();
			surface->sides[j].SetSignBits();
		}

		// Compute base vertices
		for (j = 0, vertex = surface->vertices; j < surface->numVertices; j++, vertex++){
			vertex->baseXyz.Clear();

			weight = surface->weights + vertex->firstWeight;

			for (k = 0; k < vertex->numWeights; k++, weight++){
				joint = model->joints + weight->joint;

				joint->matrix.TransformVector(weight->offset, tmp);

				vertex->baseXyz += tmp * weight->jointWeight;
			}
		}

		// Clear the transformed XYZ vectors
		surface->transformedXyz = NULL;

		// Clear the triangle planes
		surface->numTriangles = 0;

		surface->facePlanes = NULL;
		surface->edgePlanes = NULL;

		// OR in the content flags from this surface
		model->contentFlags |= surface->material->contentFlags;
	}

	// Now that we have computed the base vertices for the default pose,
	// inverse the joint matrices
	for (i = 0, joint = model->joints; i < model->numJoints; i++, joint++)
		joint->matrix = -joint->matrix;
}

/*
 ==================
 CM_LoadDynamicModelVertices
 ==================
*/
/*
static void CM_LoadDynamicModelVertices (script_t *script, const char *name, cmTriangleSurface_t *surface, int *size){

	token_t		token;
	cmVertex_t	*out;
	int			numVertices;
	int			i, index;

	// Parse the number of vertices
	if (!PS_ExpectTokenString(script, &token, "numVerts"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numVerts' (%s)", name);

	if (!PS_ReadSignedInt(script, &numVertices))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numVerts' parameter (%s)", name);

	if (numVertices < 1)
		Com_Error(false, "CM_LoadDynamicModel: no vertices (%s)", name);
	else if (numVertices > MD5_MAX_VERTICES)
		Com_Error(false, "CM_LoadDynamicModel: too many vertices (%s)", name);

	// Parse the vertices
	surface->numVertices = numVertices;
	surface->vertices = out = (cmVertex_t *)Mem_Alloc(surface->numVertices * sizeof(cmVertex_t), TAG_COLLISION);

	*size += surface->numVertices * sizeof(cmVertex_t);

	for (i = 0; i < numVertices; i++, out++){
		// Parse the vertex index
		if (!PS_ExpectTokenString(script, &token, "vert"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'vert' (%s)", name);

		if (!PS_ReadSignedInt(script, &index))
			Com_Error(false, "CM_LoadDynamicModel: missing 'vert' parameters (%s)", name);

		if (index != i)
			Com_Error(false, "CM_LoadDynamicModel: wrong 'vert' index (%i should be %i) (%s)", index, i, name);

		// Parse the first weight index
		if (!PS_ReadSignedInt(script, &out->firstWeight))
			Com_Error(false, "CM_LoadDynamicModel: missing 'vert' parameters (%s)", name);

		if (out->firstWeight < 0)
			Com_Error(false, "CM_LoadDynamicModel: invalid 'vert' parameters (%s)", name);

		// Parse the number of weights
		if (!PS_ReadSignedInt(script, &out->numWeights))
			Com_Error(false, "CM_LoadDynamicModel: missing 'vert' parameters (%s)", name);

		if (out->numWeights < 1)
			Com_Error(false, "CM_LoadDynamicModel: invalid 'vert' parameters (%s)", name);
	}
}

static void CM_LoadDynamicModelTriangles (script_t *script, const char *name, cmTriangleSurface_t *surface, int *size){

	token_t	token;
	int		*out;
	int		numTriangles;
	int		i, j, index;

	// Parse the number of triangles
	if (!PS_ExpectTokenString(script, &token, "numTris"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numTris' (%s)", name);

	if (!PS_ReadSignedInt(script, &numTriangles))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numTris' parameter (%s)", name);

	if (numTriangles < 1)
		Com_Error(false, "CM_LoadDynamicModel: no triangles (%s)", name);
	else if (numTriangles > MD5_MAX_TRIANGLES)
		Com_Error(false, "CM_LoadDynamicModel: too many triangles (%s)", name);

	// Parse the triangles
	surface->numIndices = numTriangles * 3;
	surface->indices = out = (int *)Mem_Alloc(surface->numIndices * sizeof(int), TAG_COLLISION);

	*size += surface->numIndices * sizeof(int);

	for (i = 0; i < numTriangles; i++, out += 3){
		// Parse the triangle index
		if (!PS_ExpectTokenString(script, &token, "tri"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'tri' (%s)", name);

		if (!PS_ReadSignedInt(script, &index))
			Com_Error(false, "CM_LoadDynamicModel: missing 'tri' parameters (%s)", name);

		if (index != i)
			Com_Error(false, "CM_LoadDynamicModel: wrong 'tri' index (%i should be %i) (%s)", index, i, name);

		// Parse the indices
		for (j = 0; j < 3; j++){
			if (!PS_ReadSignedInt(script, &out[j]))
				Com_Error(false, "CM_LoadDynamicModel: missing 'tri' parameters (%s)", name);

			if (out[j] >= surface->numVertices)
				Com_Error(false, "CM_LoadDynamicModel: invalid 'tri' parameters (%s)", name);
		}
	}
}

static void CM_LoadDynamicModelWeights (script_t *script, const char *name, cmTriangleSurface_t *surface, int *size){

	token_t		token;
	cmWeight_t	*out;
	int			numWeights;
	int			i, j, index;

	// Parse the number of weights
	if (!PS_ExpectTokenString(script, &token, "numWeights"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numWeights' (%s)", name);

	if (!PS_ReadSignedInt(script, &numWeights))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numWeights' parameter (%s)", name);

	if (numWeights < 1)
		Com_Error(false, "CM_LoadDynamicModel: no weights (%s)", name);
	else if (numWeights > MD5_MAX_WEIGHTS)
		Com_Error(false, "CM_LoadDynamicModel: too many weights (%s)", name);

	// Parse the weights
	surface->numWeights = numWeights;
	surface->weights = out = (cmWeight_t *)Mem_Alloc(surface->numWeights * sizeof(cmWeight_t), TAG_COLLISION);

	*size += surface->numWeights * sizeof(cmWeight_t);

	for (i = 0; i < numWeights; i++, out++){
		// Parse the weight index
		if (!PS_ExpectTokenString(script, &token, "weight"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'weight' (%s)", name);

		if (!PS_ReadSignedInt(script, &index))
			Com_Error(false, "CM_LoadDynamicModel: missing 'weight' parameters (%s)", name);

		if (index != i)
			Com_Error(false, "CM_LoadDynamicModel: wrong 'weight' index (%i should be %i) (%s)", index, i, name);

		// Parse the joint index
		if (!PS_ReadSignedInt(script, &out->joint))
			Com_Error(false, "CM_LoadDynamicModel: missing 'weight' parameters (%s)", name);

		if (out->joint < 0)
			Com_Error(false, "CM_LoadDynamicModel: invalid 'weight' parameters (%s)", name);

		// Parse the joint weight
		if (!PS_ReadFloat(script, &out->jointWeight))
			Com_Error(false, "CM_LoadDynamicModel: missing 'weight' parameters (%s)", name);

		// Parse the offset
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "CM_LoadDynamicModel: missing 'weight' parameters (%s)", name);

		for (j = 0; j < 3; j++){
			if (!PS_ReadFloat(script, &out->offset[j]))
				Com_Error(false, "CM_LoadDynamicModel: missing 'weight' parameters (%s)", name);
		}

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'weight' parameters (%s)", name);
	}
}
*/
/*
 ==================
 CM_LoadDynamicModelJoints
 ==================
*/
static void CM_LoadDynamicModelJoints (script_t *script, const char *name, cmDynamicModel_t *model, int *size){

	token_t		token;
	cmJoint_t	*out;
	odVec3		translation;
	odQuat		orientation;
	int			i, j;

	// Skip 'joints'
	if (!PS_ExpectTokenString(script, &token, "joints"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'joints' (%s)", name);

	// Skip the opening brace
	if (!PS_ExpectTokenString(script, &token, "{"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);

	// Parse the joints
	model->joints = out = (cmJoint_t *)Mem_Alloc(model->numJoints * sizeof(cmJoint_t), TAG_COLLISION);
	*size += model->numJoints * sizeof(cmJoint_t);

	for (i = 0; i < model->numJoints; i++, out++){
		// Skip the name
		if (!PS_ReadToken(script, &token))
			Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);

		// Parse the parent index
		if (!PS_ReadSignedInt(script, &out->parent))
			Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);

		if (out->parent < -1 || out->parent >= model->numJoints)
			Com_Error(false, "CM_LoadDynamicModel: invalid 'joint' parameters (%s)", name);

		// Parse the translation
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);

		for (j = 0; j < 3; j++){
			if (!PS_ReadFloat(script, &translation[j]))
				Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);
		}

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);

		// Parse the orientation
		if (!PS_ExpectTokenString(script, &token, "("))
			Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);

		for (j = 0; j < 3; j++){
			if (!PS_ReadFloat(script, &orientation[j]))
				Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);
		}

		orientation.CalcW();

		out->matrix.Set(orientation, translation);

		if (!PS_ExpectTokenString(script, &token, ")"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);
	}

	// Skip the closing brace
	if (!PS_ExpectTokenString(script, &token, "}"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'joint' parameters (%s)", name);
}

/*
 ==================
 CM_LoadDynamicModelMeshes
 ==================
*/
static void CM_LoadDynamicModelMeshes (script_t *script, const char *name, cmDynamicModel_t *model, int *size){

	token_t				token;
	cmTriangleSurface_t	*out;
	cmMaterial_t		*material;
	cmVertex_t			*vertex;
	cmWeight_t			*weight;
	int					i, j;

	// Parse the meshes
	model->surfaces = out = (cmTriangleSurface_t *)Mem_Alloc(model->numSurfaces * sizeof(cmTriangleSurface_t), TAG_COLLISION);
	*size += model->numSurfaces * sizeof(cmTriangleSurface_t);

	for (i = 0; i < model->numSurfaces; i++, out++){
		// Allocate a material
		out->material = material = (cmMaterial_t *)Mem_Alloc(sizeof(cmMaterial_t), TAG_COLLISION);
		*size += sizeof(cmMaterial_t);

		// Skip 'mesh'
		if (!PS_ExpectTokenString(script, &token, "mesh"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'mesh' (%s)", name);

		// Skip the opening brace
		if (!PS_ExpectTokenString(script, &token, "{"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'mesh' parameters (%s)", name);

		// Parse the shader
		if (!PS_ExpectTokenString(script, &token, "shader"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'shader' (%s)", name);

		if (!PS_ReadToken(script, &token))
			Com_Error(false, "CM_LoadDynamicModel: missing 'shader' parameter (%s)", name);

		Str_Copy(material->name, token.string, sizeof(material->name));

		// Parse the surface flags
		if (!PS_ExpectTokenString(script, &token, "surfaceFlags"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'surfaceFlags' (%s)", name);

		if (!PS_ReadSignedInt(script, &material->surfaceFlags))
			Com_Error(false, "CM_LoadDynamicModel: missing 'surfaceFlags' parameter (%s)", name);

		// Parse the content flags
		if (!PS_ExpectTokenString(script, &token, "contentFlags"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'contentFlags' (%s)", name);

		if (!PS_ReadSignedInt(script, &material->contentFlags))
			Com_Error(false, "CM_LoadDynamicModel: missing 'contentFlags' parameter (%s)", name);

		// Parse the vertices
		//CM_LoadDynamicModelVertices(script, name, out, size);

		// Parse the triangles
		//CM_LoadDynamicModelTriangles(script, name, out, size);

		// Parse the weights
		//CM_LoadDynamicModelWeights(script, name, out, size);

		// Skip the closing brace
		if (!PS_ExpectTokenString(script, &token, "}"))
			Com_Error(false, "CM_LoadDynamicModel: missing 'mesh' parameters (%s)", name);

		// Sanity check the vertices
		for (j = 0, vertex = out->vertices; j < out->numVertices; j++, vertex++){
			if (vertex->firstWeight + vertex->numWeights > out->numWeights)
				Com_Error(false, "CM_LoadDynamicModel: invalid 'vert' parameters (%s)", name);
		}

		// Sanity check the weights
		for (j = 0, weight = out->weights; j < out->numWeights; j++, weight++){
			if (weight->joint >= model->numJoints)
				Com_Error(false, "CM_LoadDynamicModel: invalid 'weight' parameters (%s)", name);
		}
	}
}
/*
static bool CM_LoadDynamicModel (const char *name, cmDynamicModel_t **model, int *size){

	script_t			*script;
	token_t				token;
	cmDynamicModel_t	*out;
	char				*text;
	int					version;

	// Load the file
	script = PS_LoadScriptFile(name);
	if (!script)
		return false;

	PS_SetScriptFlags(script, SF_NOWARNINGS | SF_NOERRORS | SF_ALLOWPATHNAMES);

	*model = out = (cmDynamicModel_t *)Mem_Alloc(sizeof(cmDynamicModel_t), TAG_COLLISION);
	*size = sizeof(cmDynamicModel_t);

	// Parse the version
	if (!PS_ExpectTokenString(script, &token, MD5_VERSION_STRING))
		Com_Error(false, "CM_LoadDynamicModel: missing '%s' (%s)", MD5_VERSION_STRING, name);

	if (!PS_ReadSignedInt(script, &version))
		Com_Error(false, "CM_LoadDynamicModel: missing version number (%s)", name);

	if (version != MD5_VERSION)
		Com_Error(false, "CM_LoadDynamicModel: wrong version number (%i should be %i) (%s)", version, MD5_VERSION, name);

	// Skip the command line
	if (!PS_ExpectTokenString(script, &token, "commandLine"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'commandLine' (%s)", name);

	text = Str_FindChar(script->text, '\"');
	if (text)
		text = Str_FindChar(text+1, '\"');

	if (text)
		script->text = text+1;
	else
		Com_Error(false, "CM_LoadDynamicModel: missing 'commandLine' parameters (%s)", name);

	// Parse the number of joints
	if (!PS_ExpectTokenString(script, &token, "numJoints"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numJoints' (%s)", name);

	if (!PS_ReadSignedInt(script, &out->numJoints))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numJoints' parameter (%s)", name);

	if (out->numJoints < 1)
		Com_Error(false, "CM_LoadDynamicModel: no joints (%s)", name);
	else if (out->numJoints > MD5_MAX_JOINTS)
		Com_Error(false, "CM_LoadDynamicModel: too many joints (%s)", name);

	// Parse the number of meshes
	if (!PS_ExpectTokenString(script, &token, "numMeshes"))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numMeshes' (%s)", name);

	if (!PS_ReadSignedInt(script, &out->numSurfaces))
		Com_Error(false, "CM_LoadDynamicModel: missing 'numMeshes' parameter (%s)", name);

	if (out->numSurfaces < 1)
		Com_Error(false, "CM_LoadDynamicModel: no meshes (%s)", name);
	else if (out->numSurfaces > MD5_MAX_MESHES)
		Com_Error(false, "CM_LoadDynamicModel: too many meshes (%s)", name);

	// Parse the joints
	CM_LoadDynamicModelJoints(script, name, out, size);

	// Parse the meshes
	CM_LoadDynamicModelMeshes(script, name, out, size);

	// Precompute base vertices
	CM_FinishDynamicModel(out, size);

	// Free file data
	PS_FreeScript(script);

	return true;
}
*/

/*
 ==============================================================================

 MODEL LOADING

 ==============================================================================
*/


/*
 ==================
 CM_LoadModel
 ==================
*/
static clipModel_t *CM_LoadModel (const char *name, clipModelType_t type, void *data, int size){

	clipModel_t	*model;
	uint		hashKey;

	if (cm_numModels == MAX_CLIP_MODELS)
		Com_Error(false, "CM_LoadModel: MAX_CLIP_MODELS hit");

	cm_models[cm_numModels++] = model = (clipModel_t *)Mem_Alloc(sizeof(clipModel_t), TAG_COLLISION);

	// Fill it in
	Str_Copy(model->name, name, sizeof(model->name));
	model->type = type;
	model->data = data;
	model->size = size;

	// Add to hash table
	hashKey = Str_HashKey(model->name, CLIP_MODELS_HASH_SIZE, false);

	model->nextHash = cm_modelsHashTable[hashKey];
	cm_modelsHashTable[hashKey] = model;

	return model;
}

/*
 ==================
 CM_FindModel
 ==================
*/
static clipModel_t *CM_FindModel (const char *name){

	clipModel_t	*model;
	char		extension[16];
	void		*data;
	int			size;
	uint		hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, CLIP_MODELS_HASH_SIZE, false);

	for (model = cm_modelsHashTable[hashKey]; model; model = model->nextHash){
		if (!Str_ICompare(model->name, name))
			return model;
	}

	// Load it from disk
	Str_ExtractFileExtension(name, extension, sizeof(extension));

	if (!Str_ICompare(extension, ".cm")){
		if (CM_LoadStaticModel(name, (cmStaticModel_t **)&data, &size))
			return CM_LoadModel(name, CLIP_MODEL_STATIC, data, size);
	}

	if (!Str_ICompare(extension, ".md5clip")){
//		if (CM_LoadDynamicModel(name, (cmDynamicModel_t **)&data, &size))
//			return CM_LoadModel(name, CLIP_MODEL_DYNAMIC, data, size);
	}

	// Not found
	return NULL;
}

/*
 ==================
 CM_RegisterModel
 ==================
*/
clipModel_t *CM_RegisterModel (const char *name){

	clipModel_t	*model;

	if (!cm.loaded)
		Com_Error(false, "CM_RegisterModel: map not loaded");

	model = CM_FindModel(name);
	if (model)
		return model;

	// Register the name even if not found
	Com_Printf(S_COLOR_YELLOW "WARNING: couldn't find clip model '%s'\n", name);

	return CM_LoadModel(name, CLIP_MODEL_BAD, NULL, 0);
}


// ============================================================================


/*
 ==================
 CM_LoadInlineModel

 Used by the BSP loading code for inline models
 ==================
*/
void CM_LoadInlineModel (int index, void *data){

	char	name[8];

	Str_SPrintf(name, sizeof(name), "*%i", index);

	CM_LoadModel(name, CLIP_MODEL_INLINE, data, 0);
}

/*
 ==================
 CM_InitBoxModel

 Set up the planes so that the six floats of a bounding box can just be stored
 out and get a proper clipping hull structure
 ==================
*/
void CM_InitBoxModel (void){

	cmBrushSide_t	*side;
	odPlane			*plane;
	int				i;

	cm_boxInlineModel.leaf.cluster = 0;
	cm_boxInlineModel.leaf.area = 0;
	cm_boxInlineModel.leaf.canSeePortalSky = false;
	cm_boxInlineModel.leaf.numLeafBrushes = BOX_MODEL_BRUSHES;
	cm_boxInlineModel.leaf.firstLeafBrush = cm.numLeafBrushes;
	cm_boxInlineModel.leaf.numLeafSurfaces = 0;
	cm_boxInlineModel.leaf.firstLeafSurface = 0;

	cm_boxSides = cm_boxInlineModel.sides;
	cm_boxMaterial = &cm.materials[cm.numMaterials];
	cm_boxPlanes = &cm.planes[cm.numPlanes];
	cm_boxBrush = &cm.brushes[cm.numBrushes];

	// Set up the sides
	for (i = 0, plane = cm_boxSides; i < 6; i++, plane++){
		plane->normal.Clear();

		if (i & 1)
			plane->normal[i>>1] = -1.0f;
		else
			plane->normal[i>>1] = 1.0f;

		plane->SetType();
		plane->SetSignBits();
	}

	// Set up the material
	Str_Copy(cm_boxMaterial->name, "*** BoxModel ***", sizeof(cm_boxMaterial->name));

	// Set up the planes
	for (i = 0, plane = cm_boxPlanes; i < 6; i++, plane++){
		plane->normal.Clear();

		if (i & 1)
			plane->normal[i>>1] = -1.0f;
		else
			plane->normal[i>>1] = 1.0f;

		plane->SetType();
		plane->SetSignBits();
	}

	// Set up the brush
	cm_boxBrush->material = cm_boxMaterial;
	cm_boxBrush->numSides = BOX_MODEL_BRUSHSIDES;
	cm_boxBrush->sides = &cm.brushSides[cm.numBrushSides];
	cm_boxBrush->contentsCount = 0;
	cm_boxBrush->traceCount = 0;

	cm.leafBrushes[cm.numLeafBrushes] = cm.numBrushes;

	for (i = 0; i < 6; i++){
		side = &cm.brushSides[cm.numBrushSides + i];

		side->material = cm_boxMaterial;
		side->plane = &cm_boxPlanes[i];
	}

	// Load the model
	cm_boxModel = CM_LoadModel("*** BoxModel ***", CLIP_MODEL_INLINE, &cm_boxInlineModel, 0);
}

/*
 ==================
 CM_SetupBoxModel

 To keep everything totally uniform, bounding boxes are turned into inline
 models instead of being compared directly
 ==================
*/
clipModel_t *CM_SetupBoxModel (const odBounds &bounds, int surfaceFlags, int contentFlags){

	if (!cm.loaded)
		Com_Error(false, "CM_SetupBoxModel: map not loaded");

	cm_boxInlineModel.bounds = bounds;

	cm_boxInlineModel.leaf.contentFlags = contentFlags;

	// Set up the sides
	cm_boxSides[0].dist = bounds[1][0];
	cm_boxSides[1].dist = -bounds[0][0];
	cm_boxSides[2].dist = bounds[1][1];
	cm_boxSides[3].dist = -bounds[0][1];
	cm_boxSides[4].dist = bounds[1][2];
	cm_boxSides[5].dist = -bounds[0][2];

	// Set up the material
	cm_boxMaterial->surfaceFlags = surfaceFlags;
	cm_boxMaterial->contentFlags = contentFlags;

	// Set up the planes
	cm_boxPlanes[0].dist = bounds[1][0];
	cm_boxPlanes[1].dist = -bounds[0][0];
	cm_boxPlanes[2].dist = bounds[1][1];
	cm_boxPlanes[3].dist = -bounds[0][1];
	cm_boxPlanes[4].dist = bounds[1][2];
	cm_boxPlanes[5].dist = -bounds[0][2];

	// Set up the brush
	cm_boxBrush->bounds = bounds;

	return cm_boxModel;
}

/*
 ==================
 CM_InstantiateDynamicModel

 Returns a static model instance for a dynamic model
 ==================
*/
/*
clipModel_t *CM_InstantiateDynamicModel (clipModel_t *model, int entityNum, odJointMat *joints){

	clipModel_t			*instance;
	cmDynamicModel_t	*cachedModel;
	cmTriangleSurface_t	*cachedSurface;
	cmDynamicModel_t	*instancedModel;
	cmTriangleSurface_t	*instancedSurface;
	odJointMat			jointMatrices[MD5_MAX_JOINTS];
	int					i;

	if (!cm.loaded)
		Com_Error(false, "CM_InstantiateDynamicModel: map not loaded");

	if (!model)
		Com_Error(false, "CM_InstantiateDynamicModel: NULL model");

	if (model->type != CLIP_MODEL_DYNAMIC)
		Com_Error(false, "CM_InstantiateDynamicModel: invalid model type for '%s'", model->name);

	// See if we have already cached this instance
	if (entityNum < 0 || entityNum >= MAX_ENTITIES)
		Com_Error(false, "CM_InstantiateDynamicModel: entityNum out of range for '%s'", model->name);

	if (cm_modelInstances[entityNum])
		return cm_modelInstances[entityNum];

	cachedModel = (cmDynamicModel_t *)model->data;

	// Multiply the joints
	CM_MultiplyJoints(cachedModel->numJoints, cachedModel->joints, joints, jointMatrices);

	// Instantiate the model
	instancedModel = (cmDynamicModel_t *)Mem_Alloc(sizeof(cmDynamicModel_t), TAG_COLLISION);

	instancedModel->contentFlags = cachedModel->contentFlags;

	instancedModel->bounds.Clear();

	instancedModel->numJoints = cachedModel->numJoints;
	instancedModel->joints = cachedModel->joints;

	instancedModel->numSurfaces = cachedModel->numSurfaces;
	instancedModel->surfaces = instancedSurface = (cmTriangleSurface_t *)Mem_Alloc(instancedModel->numSurfaces * sizeof(cmTriangleSurface_t), TAG_COLLISION);

	// Instantiate all the surfaces
	for (i = 0, cachedSurface = cachedModel->surfaces; i < cachedModel->numSurfaces; i++, cachedSurface++){
		// Copy the material
		instancedSurface->material = cachedSurface->material;

		// Indices just point back at the original surface indices
		instancedSurface->numIndices = cachedSurface->numIndices;
		instancedSurface->indices = cachedSurface->indices;

		// Vertices just point back at the original surface vertices
		instancedSurface->numVertices = cachedSurface->numVertices;
		instancedSurface->vertices = cachedSurface->vertices;

		// Weights just point back at the original surface weights
		instancedSurface->numWeights = cachedSurface->numWeights;
		instancedSurface->weights = cachedSurface->weights;

		// Allocate the transformed XYZ vectors
		instancedSurface->transformedXyz = (odVec3 *)Mem_Alloc(instancedSurface->numVertices * sizeof(odVec3), TAG_COLLISION);

		// Allocate the triangle planes
		instancedSurface->numTriangles = cachedSurface->numIndices / 3;

		instancedSurface->facePlanes = (odPlane *)Mem_Alloc(instancedSurface->numTriangles * sizeof(odPlane), TAG_COLLISION);
		instancedSurface->edgePlanes = (odPlane *)Mem_Alloc(instancedSurface->numTriangles * 3 * sizeof(odPlane), TAG_COLLISION);

		// Compute triangle surface
		CM_ComputeTriangleSurface(instancedSurface, jointMatrices);

		// Also add to bounds
		instancedModel->bounds.AddBounds(instancedSurface->bounds);

		instancedSurface++;
	}

	// Set up the sides
	for (i = 0; i < 6; i++){
		instancedModel->sides[i].normal.Clear();

		if (i & 1){
			instancedModel->sides[i].normal[i>>1] = -1.0f;
			instancedModel->sides[i].dist = -instancedModel->bounds[0][i>>1];
		}
		else {
			instancedModel->sides[i].normal[i>>1] = 1.0f;
			instancedModel->sides[i].dist = instancedModel->bounds[1][i>>1];
		}

		instancedModel->sides[i].SetType();
		instancedModel->sides[i].SetSignBits();
	}

	// Set up the new model instance
	cm_modelInstances[entityNum] = instance = (clipModel_t *)Mem_Alloc(sizeof(clipModel_t), TAG_COLLISION);

	Str_Copy(instance->name, model->name, sizeof(instance->name));
	instance->type = model->type;
	instance->data = instancedModel;
	instance->size = model->size;

	instance->nextHash = model->nextHash;

	// Mark for deferred free
	cm_deferredFreeInstances[cm_numModelInstances++] = entityNum;

	return instance;
}
*/
/*
 ==================
 CM_ClearDynamicModelInstances
 ==================
*/
void CM_ClearDynamicModelInstances (void){

	clipModel_t			*instance;
	cmDynamicModel_t	*instancedModel;
	cmTriangleSurface_t	*instancedSurface;
	int					i, j;

	for (i = 0; i < cm_numModelInstances; i++){
		instance = cm_modelInstances[cm_deferredFreeInstances[i]];
		if (!instance)
			continue;

		instancedModel = (cmDynamicModel_t *)instance->data;

		// Free the transformed XYZ vectors and triangle planes
		for (j = 0, instancedSurface = instancedModel->surfaces; j < instancedModel->numSurfaces; j++, instancedSurface++){
			Mem_Free(instancedSurface->transformedXyz);

			Mem_Free(instancedSurface->facePlanes);
			Mem_Free(instancedSurface->edgePlanes);
		}

		// Free the surfaces and model
		Mem_Free(instancedModel->surfaces);
		Mem_Free(instancedModel);

		// Clear the model instance
		Mem_Free(instance);

		cm_modelInstances[cm_deferredFreeInstances[i]] = NULL;
	}

	cm_numModelInstances = 0;
}

/*
 ==================
 CM_GetModelContents
 ==================
*/
int CM_GetModelContents (clipModel_t *model){

	if (!cm.loaded)
		Com_Error(false, "CM_GetModelContents: map not loaded");

	switch (model->type){
	case CLIP_MODEL_INLINE:
		return ((cmInlineModel_t *)model->data)->leaf.contentFlags;
	case CLIP_MODEL_STATIC:
		return ((cmStaticModel_t *)model->data)->contentFlags;
	case CLIP_MODEL_DYNAMIC:
		return ((cmDynamicModel_t *)model->data)->contentFlags;
	}

	return 0;
}

/*
 ==================
 CM_GetModelBounds
 ==================
*/
void CM_GetModelBounds (clipModel_t *model, odBounds &bounds){

	if (!cm.loaded)
		Com_Error(false, "CM_GetModelBounds: map not loaded");

	switch (model->type){
	case CLIP_MODEL_INLINE:
		bounds = ((cmInlineModel_t *)model->data)->bounds;
		return;
	case CLIP_MODEL_STATIC:
		bounds = ((cmStaticModel_t *)model->data)->bounds;
		return;
	case CLIP_MODEL_DYNAMIC:
		bounds = ((cmDynamicModel_t *)model->data)->bounds;
		return;
	}

	bounds.Zero();
}

/*
 ==================
 CM_FreeModels
 ==================
*/
void CM_FreeModels (void){

	// Clear model instances
	memset(cm_modelInstances, 0, sizeof(cm_modelInstances));

	cm_numModelInstances = 0;

	// Clear model list
	memset(cm_modelsHashTable, 0, sizeof(cm_modelsHashTable));
	memset(cm_models, 0, sizeof(cm_models));

	cm_numModels = 0;
}

/*
 ==================
 CM_ClipMapInfo_f
 ==================
*/
static void CM_ClipMapInfo_f (void){

	cmSurface_t	*surface;
	int			numSurfaces = 0, surfaceBytes = 0;
	int			i;

	if (!cm.loaded){
		Com_Printf("Clip map not loaded\n");
		return;
	}

	// Surfaces are special, so we need to count them here
	for (i = 0; i < cm.numSurfaces; i++){
		surface = cm.surfaces[i];

		if (!surface)
			continue;		// No collision data

		numSurfaces++;
		surfaceBytes += Mem_Size(surface);
	}

	Com_Printf("\n");
	Com_Printf("%s\n", cm.name);
	Com_Printf("----------------------------------------\n");
	Com_Printf("%5i KB in %8i materials\n", Mem_Size(cm.materials) >> 10, cm.numMaterials);
	Com_Printf("%5i KB in %8i planes\n", Mem_Size(cm.planes) >> 10, cm.numPlanes);
	Com_Printf("%5i KB in %8i brush sides\n", Mem_Size(cm.brushSides) >> 10, cm.numBrushSides);
	Com_Printf("%5i KB in %8i brushes\n", Mem_Size(cm.brushes) >> 10, cm.numBrushes);
	Com_Printf("%5i KB in %8i surface planes\n", (cm.numSurfacePlanes) ? (Mem_Size(cm.surfacePlanes) >> 10) : 0, cm.numSurfacePlanes);
	Com_Printf("%5i KB in %8i surface facets\n", (cm.numSurfaceFacets) ? (Mem_Size(cm.surfaceFacets) >> 10) : 0, cm.numSurfaceFacets);
	Com_Printf("%5i KB in %8i surfaces\n", surfaceBytes >> 10, numSurfaces);
	Com_Printf("%5i KB in %8i leaf brushes\n", Mem_Size(cm.leafBrushes) >> 10, cm.numLeafBrushes);
	Com_Printf("%5i KB in %8i leaf surfaces\n", Mem_Size(cm.leafSurfaces) >> 10, cm.numLeafSurfaces);
	Com_Printf("%5i KB in %8i leafs\n", Mem_Size(cm.leafs) >> 10, cm.numLeafs);
	Com_Printf("%5i KB in %8i nodes\n", Mem_Size(cm.nodes) >> 10, cm.numNodes);
	Com_Printf("%5i KB in %8i inline models\n", Mem_Size(cm.inlineModels) >> 10, cm.numInlineModels);
	Com_Printf("%5i KB in %8i PVS clusters\n", (cm.pvs) ? (Mem_Size(cm.pvs) >> 10) : 0, cm.numClusters);
	Com_Printf("%5i KB in %8i portals\n", (cm.numPortals) ? (Mem_Size(cm.portals) >> 10) : 0, cm.numPortals);
	Com_Printf("%5i KB in %8i area portals\n", (cm.numAreaPortals) ? (Mem_Size(cm.areaPortals) >> 10) : 0, cm.numAreaPortals);
	Com_Printf("%5i KB in %8i areas\n", Mem_Size(cm.areas) >> 10, cm.numAreas);
	Com_Printf("----------------------------------------\n");
	Com_Printf("%.2f MB of clip map data\n", cm.size * (1.0f/1048576));
	Com_Printf("\n");
}

/*
 ==================
 CM_ListClipModels_f
 ==================
*/
static void CM_ListClipModels_f (void){

	clipModel_t			*model;
	cmInlineModel_t		*inlineModel;
	cmStaticModel_t		*staticModel;
	cmDynamicModel_t	*dynamicModel;
	int					bytes = 0;
	int					i;

	if (!cm.loaded){
		Com_Printf("Clip map not loaded\n");
		return;
	}

	Com_Printf("\n");
	Com_Printf("      brush surfs -size- -name--------\n");

	for (i = 0; i < cm_numModels; i++){
		model = cm_models[i];

		bytes += model->size;

		Com_Printf("%4i: ", i);

		switch (model->type){
		case CLIP_MODEL_INLINE:
			inlineModel = (cmInlineModel_t *)model->data;

			Com_Printf("%5i %5i ", inlineModel->leaf.numLeafBrushes, inlineModel->leaf.numLeafSurfaces);

			break;
		case CLIP_MODEL_STATIC:
			staticModel = (cmStaticModel_t *)model->data;

			Com_Printf("%5i %5i ", 0, staticModel->numSurfaces);

			break;
		case CLIP_MODEL_DYNAMIC:
			dynamicModel = (cmDynamicModel_t *)model->data;

			Com_Printf("%5i %5i ", 0, dynamicModel->numSurfaces);

			break;
		}

		Com_Printf("%5ik ", model->size >> 10);

		Com_Printf("%s%s\n", model->name, (model->type == CLIP_MODEL_BAD) ? " (DEFAULTED)" : "");
	}

	Com_Printf("--------------------------------------\n");
	Com_Printf("%i total clip models\n", cm_numModels);
	Com_Printf("%.2f MB of clip model data\n", bytes * (1.0f/1048576));
	Com_Printf("\n");
}

/*
 ==================
 CM_InitModels
 ==================
*/
void CM_InitModels (void){

	// Register our commands
	Cmd_AddCommand("clipMapInfo", CM_ClipMapInfo_f, "Shows clip map information");
	Cmd_AddCommand("listClipModels", CM_ListClipModels_f, "Lists loaded clip models");
}

/*
 ==================
 CM_ShutdownModels
 ==================
*/
void CM_ShutdownModels (void){

	// Unregister our commands
	Cmd_RemoveCommand("clipMapInfo");
	Cmd_RemoveCommand("listClipModels");

	// Clear model instances
	memset(cm_modelInstances, 0, sizeof(cm_modelInstances));

	cm_numModelInstances = 0;

	// Clear model list
	memset(cm_modelsHashTable, 0, sizeof(cm_modelsHashTable));
	memset(cm_models, 0, sizeof(cm_models));

	cm_numModels = 0;
}
